# 导入常用库
import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder
# 读取csv文件并存储到dataframe里面
data = pd.read_csv('./mushrooms.csv')
data.head()
从蘑菇数据样本中,我们可以看到数据的一些特征:
特种中包含帽型、帽面、帽色等植物学相关的特征,我们这里并不需要研究植物学之后再对这些数据进行处理,我们先将这些特征都作为数据的不同维度属性来平等对待。
现在就让我们来观察一下数据中每一列的数字分布情况吧!
import matplotlib.pyplot as plt
from math import ceil
import seaborn as sns
%matplotlib inline
font = {'family' : 'Times New Roman',
'weight' : 'normal',
'size' : 23}
plt.figure(figsize=(30, 150))
# 将要绘制图形的每行由多少个直方图组成
column_per_line = 3
# 属性的总数
columns = len(data.columns)
n_row = ceil(columns/column_per_line)
for i, attr in enumerate(data.columns):
# TODO
# 绘制columns个直方图(每个属性一个图),描绘每个属性中不同值的分布情况。直方图最好绘制成三列的排布方式,方便在如下结果窗中展示。
# 可以使用如上定义的font字体让显示的字体更清楚些。
#数据简单处理
data_to_plt = data[attr].value_counts()
#画图
plt.subplot(n_row, column_per_line, i+1)
data_to_plt.plot.bar()
plt.title(attr, fontdict=font)
plt.show()
从结果中,可以看到,哪一列属性只有一个值,不具备区分度?
回答: 不具备区分度的列是 veil-type
# TODO
# 从data中将这列没有区分度的属性去除掉
"""画不出图,直接判断好了"""
for col in data.columns:
if len(data[col].unique()) == 1:
print('属性只有一列的是:', col)
col_k = col
break
data = data.drop(col_k, axis=1)
data.shape
# 使用sklearn中的LabelEncoder方法对仅有两个可能值的变量进行编码操作
for col in data.columns:
# TODO
# 判断col这列是否只有两个值,如果是,使用LabelEncoder对其进行转换并更新到data相应列中
if len(data[col].unique()) == 2:
label_enc = LabelEncoder()
data[col] = label_enc.fit_transform(data[col])
# 打印转换之后的数据头部
data.head()
对于多值属性,标签编码以及独热编码两个步骤,在Pandas当中,我们可以直接使用get_dummies一步完成:
# 使用pandas中的get_dummies方法对属性进行独热编码
data = pd.get_dummies(data)
data.head()
import time
from sklearn.metrics import silhouette_score
from sklearn.cluster import KMeans
def cluster(X):
'''
K-Means聚类执行函数。
输入
X:待处理的数据。
输出
best_clusters:分数(轮廓系数)最高的分类器
score:分类器在数据上的轮廓系数
'''
score = -1
duration = 0
start = time.time()
# 取K值为2到15
for clusters_num in range(2, 15):
# TODO
# 建立kmeans模型,其中聚类数等于clusters_num
kmeans = KMeans(n_clusters=clusters_num)
# TODO
# 使用kmeans模型对数据X进行拟合,并用该模型对数据X做聚类预测,存储到test_preds变量中
kmeans.fit(X)
test_preds = kmeans.predict(X)
# TODO
# 使用silhouette_score计算当前模型预测所得的轮廓系数并赋值到变量test_score中
test_score = silhouette_score(X, test_preds)
# 打印计算的轮廓系数
print("component score is ", test_score)
#将效果最好的模型中的参数保存起来
if (score < test_score):
best_clustersnum = clusters_num
score = test_score
best_clusters=kmeans
# 统计时间
end = time.time()
duration += end - start
# 打印结果
print('训练模型耗时: {:.4f}s'.format(duration))
print('当k为{}的时候,轮廓系数最高,为{:.3f}'.format(best_clustersnum, score))
return best_clusters, score
# 调用cluster函数对data数据做聚类处理
clu, score = cluster(data)
from sklearn.decomposition import PCA
# TODO
# 声明维度为20的PCA对象
pca = PCA(n_components=20)
# 对data进行拟合
pca.fit(data)
# pca.components_中记录了按照解释方差比从大到小排列主成分
# 使用柱形图绘制每个主成分与原数据特征之间的关系
components = pd.DataFrame(np.round(pca.components_, 4), columns = [data.keys()])
components.plot.bar(figsize=(20, 6))
由于原数据经过独热编码处理之后,维度达到一百多,以上图形看起来并不是那么美观。不过并不影响我们理解:经过PCA转换之后返回的pca.components_表示按照方差解释比从大到小排列的数据的主成分。我们从图中看到,每个主成分都与原有的数据特征之间存在一定的关系。
我们不妨多做一点点研究,尝试绘制一下20个主成分与前两个维度(bruises、gill-attachment)之间的关系。
# TODO
# 使用类似如上图形绘制的方法,绘制20个主成分与前两个维度(bruises、gill-attachment)之间的关系
components_f2 = components.iloc[:, :2]
components_f2.plot.bar(figsize=(20, 6))
问题:从结果图中分析一下,在所以PCA的主成分(component)中,编号为多少的component与bruises的相关性最大。
注意:每个原特征与主成分的相关性具有正和负两个不同的方向,当这里看到的相关性值绝对值越大,表示原属性与主成分之间的关系越大。参考:https://onlinecourses.science.psu.edu/stat505/node/54/
回答: 0
接下来,我们计算一下pca中各主成分的方差解释比,以及pca转化之后所有的主成分所能解释的原数据的方差解释比之和。
# TODO
# 取出pca转换之后各主成分的方差解释比,存在变量var_ratio中
# var_ratio中的元素从大到小排列,分别表示排名最靠前一直到最靠后的方差解释比值各是多少。
var_ratio = -np.sort(-pca.explained_variance_ratio_)
# 打印pca各component的方差解释比
print(var_ratio)
# TODO
# 打印所有方差解释比之和
print(sum(var_ratio))
这里我们期望做个探索,当选取最少的前多少个主成分时,方差解释比就可以达到70%以上。
# TODO
# 逐个计算var_ratio中的各元素之和,当至少取前多少个元素时,和大于0.7
var_ratio_sum=0
for i in range(len(var_ratio)):
if var_ratio_sum <= 0.7:
var_ratio_sum += var_ratio[i]
else:
print('选取最少前%s个时,可以达到70%%以上'% i)
break
问题:当选取最少的前多少个主成分时,方差解释比就可以达到70%以上?
回答: 14
按照这个成分数量定义,我们重新做下PCA转换:
# TODO
# 重新定义PCA变量pca,主成分数量定义为上面计算的使得解释方差比之和大于0.7的最少特征数。
pca = PCA(n_components=14)
# TODO
# 使用pca对数据data进行拟合
pca.fit(data)
现在我们来看一看,降维之后模型的效果怎么样吧!
# 使用pca对data进行转换并重新做聚类计算
data_pca = pca.transform(data)
clu, score = cluster(data_pca)
问题 : 在降维后,由于维度变小节省了计算时间,模型的分数(轮廓系数)是否受到了不好的影响?
回答: 没有,甚至有所提高
现在我们就将聚类的效果通过散点图的形式展示出来,其中,点的颜色表明它属于的簇,数字代表该簇的簇内中心点,这些中心(或者叫平均点)并不是数据中真实存在的点,但是是所有预测在这个簇中的数据点的平均。
为了方便观察,我们只挑选前两个主成分作为坐标轴,绘制二维的散点图。
import visuals as vs
# 使用刚才最优化模型对data_pca数据进行预测,结果存到变量preds中
preds = clu.predict(data_pca)
# TODO
# 从聚类模型cluster中获取各聚类中心点,结果存到变量centers中
centers = clu.cluster_centers_
# 绘制散点图
vs.cluster_results(data_pca,preds,centers)
# 加载并打印mushrooms_o.csv中的数据
data_full = pd.read_csv('./mushrooms_o.csv')
data_full.head()
这份数据中的class这一列,在任务开始之初被我们拿掉了。
这列其实在原始数据中,表示了蘑菇所属的分类,p表示有毒、e表示可以食用。
from sklearn.preprocessing import LabelEncoder
# TODO
# 将class这列从data_full中提取出来,并用LableEncoder做编码转换,转码之后结果存在变量class_label中
encoder = LabelEncoder()
class_label = encoder.fit_transform(data_full['class'])
# 输出一下class_label的前3个元素
print(class_label[:3])
问题:通过LabelEncoder编码之后,p和e所对应的编码分别是什么?
回答: e--1, p--0
# 绘制原始数据在pca前两个主成分坐标系中的分类图
vs.real_results(data_pca,class_label)
虽然在建立KMeans模型之初我们并不知道数据中存在分类,但通过聚类之后,我们会发现所聚出的簇跟蘑菇是否有毒的分类存在有趣的相关性。
问题:在原始数据的有毒蘑菇中,从图中可以大致对应到我们通过KMeans聚出来的那几个簇?
回答: 0, 2, 6
小补充:
数据通过聚类算法处理获取的聚类特征,在实际项目中可以作为原有特征的一个附加特征列加入到数据中。
这样之后,将重新组合出来的数据作为监督学习(比如分类)输入数据,往往可以获取到比只使用原有特征进行计算更好的结果。
在设置PCA主成分数量的时候,我们在以上操作中通过循环尝试选择了能保留70%多方差解释比的主成分数量,在最终的聚类与原始数据的分类分布上还存在一些差异。
在构建PCA对象的时候,传入的n_components参数如果值为一个小于1的小数,将表示在PCA转换中,保留多大比例的方差解释比。
在以下代码中,尝试定义一个保留百分之九十九(0.99)方差解释比的PCA,并用它对数据进行处理,最后同样绘制在前两个主成分坐标系中的散点图,对比聚类效果与原始数据的分类状况的近似情况。
# TODO
import visuals as vs
# 构建保留百分之九十九方差解释比的PCA并对data进行拟合转换
# 然后使用转换之后的数据训练一个KMeans模型,并绘制数据在PCA转换之后在前两个主成分坐标上的聚类分布散点图
pca_99 = PCA(n_components=0.99)
pca_99.fit(data)
data_pca_99 = pca.transform(data)
clu, score = cluster(data_pca_99)
preds_99 = clu.predict(data_pca_99)
centers = clu.cluster_centers_
# 绘制散点图
vs.cluster_results(data_pca_99, preds, centers)